/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package io.simple.core.handlers;

import com.google.common.io.BaseEncoding;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.simple.core.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import protoson.GPBUtils;
import simple.utils.concurrent.ConcurrentProperties;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Set;

/**
 * @author Tom
 */
public class MessageDecoder {

    private static String gpb2json(Message response) throws Exception {
        String json = GPBUtils.gpb2json(response);
        LOGGER.debug("Transcoding GPB " + response + " to JSON : " + json);
        return json;
    }

    public enum MessageFormat {

        GPB, JSON
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageDecoder.class);
    public static final String CONTENT_TYPE = "Content-Type";
    private static final String GPB = "application/x-protobuf";
    private static final String JSON = "application/json";
    private static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.JSON;

    private MessageDecoder() {
        throw new IllegalStateException("Can't create instance!");
    }

    public static ByteBuffer encodeResponse(Message response, RequestContext context) {
        MessageFormat format = getFormat(context);
        switch (format) {
            case GPB:
                LOGGER.debug("Encoding response " + response + " to GPB");
                if (context.getResponseHeaders().get(CONTENT_TYPE) == null) {
                    context.getResponseHeaders().set(CONTENT_TYPE, GPB);
                }
                return ByteBuffer.wrap(response.toByteArray());
            case JSON:
                try {
                    String json = gpb2json(response);
                    LOGGER.debug("Encoding response " + response + " to JSON " + json);
                    if (context.getResponseHeaders().get(CONTENT_TYPE) == null) {
                        context.getResponseHeaders().set(CONTENT_TYPE, JSON);
                    }
                    return ByteBuffer.wrap(json.getBytes("UTF-8"));
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            default:
                LOGGER.debug("Unknown response format!");
                throw new IllegalArgumentException("Unknown format " + format);
        }
    }

    // find header case insensitive
    public static String getHeader(String headerName, ConcurrentProperties<String> props) {
        Set<String> headers = props.getPropNames();
        for (String header : headers) {
            if (header.trim().equalsIgnoreCase(headerName)) {
                return props.get(header);
            }
        }
        return null;
    }

    public static MessageFormat getFormat(RequestContext context) {
        String ct = getHeader(CONTENT_TYPE, context.getRequestHeaders());
        if (ct == null) {
            LOGGER.debug("No format specified in header, using default " + DEFAULT_MESSAGE_FORMAT);
            return DEFAULT_MESSAGE_FORMAT;
        }
        ct = ct.toLowerCase();
        if (ct.contains(GPB)) {
            LOGGER.debug("GPB format specified in header");
            return MessageFormat.GPB;
        } else if (ct.contains(JSON)) {
            LOGGER.debug("JSON format specified in header");
            return MessageFormat.JSON;
        } else {
            throw new IllegalArgumentException("Unknown " + CONTENT_TYPE + " value: " + ct);
        }
    }

    public static <E extends Message> E decode(E prototype, ByteBuffer payload, RequestContext context) {
        ByteString bs = ByteString.copyFrom(payload);
        MessageFormat format = getFormat(context);
        switch (format) {
            case GPB:
                return processGPB(prototype, bs);
            case JSON:
                return processJSON(prototype, bs);
            default:
                throw new IllegalArgumentException("Unknown format " + format);
        }
    }

    private static <E extends Message> E processJSON(E prototype, ByteString bs) {
        LOGGER.debug("Translating " + bs + " (" + BaseEncoding.base32Hex().encode(bs.toByteArray()) + ") to " + prototype.getClass());
        String jsonS = bs.toStringUtf8();
        try {
            E fromJSON = GPBUtils.json2gpb(jsonS, prototype);
            LOGGER.debug("Translated to GPB " + fromJSON);
            return fromJSON;
        } catch (IOException ex) {
            LOGGER.error("Error parsing message " + prototype.getClass() + " from " + jsonS + "", ex);
            throw new RuntimeException("Error parsing message", ex);
        }
    }

    private static <E extends Message> E processGPB(E prototype, ByteString bs) {
        try {
            LOGGER.debug("Parsing " + bs + " as GPB " + prototype.getClass());
            return (E) prototype.getDefaultInstanceForType().toBuilder().mergeFrom(bs).build();
        } catch (InvalidProtocolBufferException ex) {
            LOGGER.error("Error parsing msg " + prototype.getClass() + " from " + bs, ex);
            throw new RuntimeException("Error parsing message", ex);
        }
    }
}
